package nsfs

import (
	"GLibHac/fssystem"
	"github.com/pkg/errors"
	"io"
	"math"
)

type CacheBlock struct {
	Index  int64
	Buffer []byte
	Length int32
	Dirty  bool
}

type CachedStorage struct {
	fssystem.IStorageAbstract
	baseStorage fssystem.IStorage
	blockSize   int32
	length      int64
	block       *CacheBlock
}

func (i *CachedStorage) Read(offset int64, destination []byte) (int, error) {
	remaining := int32(len(destination))
	inOffset := offset
	outOffset := int32(0)

	if !i.CheckAccessRange(offset, int64(remaining), i.length) {
		return 0, errors.WithStack(io.EOF)
	}
	for remaining > 0 {
		blockIndex := inOffset / int64(i.blockSize)
		blockPos := int32(inOffset % int64(i.blockSize))
		block, err := i.GetBlock(blockIndex)
		if err != nil {
			return 0, err
		}

		bytesToRead := int32(math.Min(float64(remaining), float64(i.blockSize-blockPos)))
		copy(destination[outOffset:], block.Buffer[blockPos:blockPos+bytesToRead])

		outOffset += bytesToRead
		inOffset += int64(bytesToRead)
		remaining -= bytesToRead
	}
	return len(destination), nil
}

func (i *CachedStorage) Size() int64 {
	return i.length
}

func (i *CachedStorage) CheckAccessRange(offset int64, size int64, totalSize int64) bool {
	return offset >= 0 &&
		size >= 0 &&
		size <= totalSize &&
		offset <= totalSize-size
}

func (i *CachedStorage) GetBlock(blockIndex int64) (*CacheBlock, error) {
	err := i.readBlock(i.block, blockIndex)
	if err != nil {
		return nil, err
	}
	return i.block, nil
}

func (i *CachedStorage) readBlock(block *CacheBlock, index int64) error {
	offset := index * int64(i.blockSize)
	length := i.blockSize
	if i.length != -1 {
		length = int32(math.Min(float64(i.length-offset), float64(length)))
	}
	_, err := i.baseStorage.Read(offset, block.Buffer[:length])
	if err != nil {
		return err
	}
	block.Length = length
	block.Index = index
	block.Dirty = false
	return nil
}

func NewCachedStorage(data fssystem.IStorage, blockSize, cacheSize int32) *CachedStorage {
	s := &CachedStorage{
		baseStorage: data,
		blockSize:   blockSize,
		length:      data.Size(),
		block: &CacheBlock{
			Buffer: make([]byte, blockSize),
			Index:  -1,
		},
	}
	s.ReadFunc = s.Read
	s.SizeFunc = s.Size
	return s
}
